package main

import (
	"fmt"
	"log"
	"os"
)

const (
	LogLevelFatal = iota
	LogLevelError
	LogLevelInfo
	LogLevelDebug
	LogLevelAll = 99
)

type Logger struct {
	level     uint8
	log       *log.Logger
	calldepth int
}

func NewLogger(level uint8, logFile *os.File) *Logger {
	if logFile == nil {
		logFile = os.Stdout
	}
	var logger *Logger = &Logger{
		level:     level,
		log:       log.New(logFile, "", log.LstdFlags),
		calldepth: 2,
	}
	if err := logger.SetLevel(level); err != nil {
		log.Fatal(err.Error())
	}
	return logger
}

// level
func (l *Logger) Level() uint8 {
	return l.level
}

// Carefully, SetLevel is NOT thread safe!
func (l *Logger) SetLevel(level uint8) error {
	if level > 3 {
		return fmt.Errorf("fatal error: invalid logger level: %s", level)
	}
	l.level = level
	if LogLevelDebug == level {
		l.log.SetFlags(log.LstdFlags | log.Lshortfile)
	}
	return nil
}

// fatal
func (l *Logger) Fatalf(format string, v ...interface{}) {
	if LogLevelFatal > l.level {
		return
	}
	l.log.Output(l.calldepth, "[FATAL] "+fmt.Sprintf(format, v...))
	os.Exit(1)
}

func (l *Logger) Fatalln(v ...interface{}) {
	if LogLevelFatal > l.level {
		return
	}
	l.log.Output(l.calldepth, "[FATAL] "+fmt.Sprintln(v...))
	os.Exit(1)
}

func (l *Logger) Fatal(v ...interface{}) {
	if LogLevelFatal > l.level {
		return
	}
	l.log.Output(l.calldepth, "[FATAL] "+fmt.Sprint(v...))
	os.Exit(1)
}

// error
func (l *Logger) Errorf(format string, v ...interface{}) {
	if LogLevelError > l.level {
		return
	}
	l.log.Output(l.calldepth, "[ERROR] "+fmt.Sprintf(format, v...))
}

func (l *Logger) Errorln(v ...interface{}) {
	if LogLevelError > l.level {
		return
	}
	l.log.Output(l.calldepth, "[ERROR] "+fmt.Sprintln(v...))
}

func (l *Logger) Error(v ...interface{}) {
	if LogLevelError > l.level {
		return
	}
	l.log.Output(l.calldepth, "[ERROR] "+fmt.Sprint(v...))
}

// info
func (l *Logger) Infof(format string, v ...interface{}) {
	if LogLevelInfo > l.level {
		return
	}
	l.log.Output(l.calldepth, "[INFO] "+fmt.Sprintf(format, v...))
}

func (l *Logger) Infoln(v ...interface{}) {
	if LogLevelInfo > l.level {
		return
	}
	l.log.Output(l.calldepth, "[INFO] "+fmt.Sprintln(v...))
}

func (l *Logger) Info(v ...interface{}) {
	if LogLevelInfo > l.level {
		return
	}
	l.log.Output(l.calldepth, "[INFO] "+fmt.Sprint(v...))
}

// debug
func (l *Logger) Debugf(format string, v ...interface{}) {
	if LogLevelDebug > l.level {
		return
	}
	l.log.Output(l.calldepth, "[DEBUG] "+fmt.Sprintf(format, v...))
}

func (l *Logger) Debugln(v ...interface{}) {
	if LogLevelDebug > l.level {
		return
	}
	l.log.Output(l.calldepth, "[DEBUG] "+fmt.Sprintln(v...))
}

func (l *Logger) Debug(v ...interface{}) {
	if LogLevelDebug > l.level {
		return
	}
	l.log.Output(l.calldepth, "[DEBUG] "+fmt.Sprint(v...))
}
